package org.springframework.cloud.openfeign.ribbon;

import com.demon.framework.common.model.constants.ApiGateConstants;
import com.netflix.client.RequestSpecificRetryHandler;
import com.netflix.client.RetryHandler;
import com.netflix.client.Utils;
import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;

/**
 * 扩展 fegin 中 robbin 的重试机制 （对fegin单独使用无效）
 *
 * @author Demon-HY
 * @date 2020-7-23
 */
public class FeignLoadBalancer2 extends FeignLoadBalancer {

    public FeignLoadBalancer2(ILoadBalancer lb, IClientConfig clientConfig,
                              ServerIntrospector serverIntrospector) {
        super(lb, clientConfig, serverIntrospector);
    }

    /**
     * 可以重试的接口处理
     */
    @Override
    public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
            RibbonRequest request, IClientConfig requestConfig) {
        if (this.clientConfig.get(CommonClientConfigKey.OkToRetryOnAllOperations,
                false)) {
            return new RequestSpecificRetryHandlerFixConnectTimedOut(true, true,
                    this.getRetryHandler(), requestConfig);
        }
        if (StringUtils.containsAny(request.toRequest().url(),
                "get", "Get", "search", "Search", "find", "Find", "query", "Query", "list", "List",
                ApiGateConstants.URI_IDEMPOTENT, "idempotent")) {
            return new RequestSpecificRetryHandlerFixConnectTimedOut(true,
                    true, this.getRetryHandler(), requestConfig);
        }

        return new RequestSpecificRetryHandlerFixConnectTimedOut(true, false,
                this.getRetryHandler(), requestConfig);
    }
}

/**
 * SocketTimeoutException不是一种SocketException，所以，原有的重试逻辑不能重试。
 */
class RequestSpecificRetryHandlerFixConnectTimedOut extends RequestSpecificRetryHandler {

    public RequestSpecificRetryHandlerFixConnectTimedOut(boolean okToRetryOnConnectErrors, boolean okToRetryOnAllErrors) {
        super(okToRetryOnConnectErrors, okToRetryOnAllErrors);
    }

    public RequestSpecificRetryHandlerFixConnectTimedOut(boolean okToRetryOnConnectErrors, boolean okToRetryOnAllErrors,
                                                         RetryHandler baseRetryHandler, IClientConfig requestConfig) {
        super(okToRetryOnConnectErrors, okToRetryOnAllErrors, baseRetryHandler, requestConfig);
    }

    @Override
    public boolean isConnectionException(Throwable e) {
        return Utils.isPresentAsCause(e, connectionRelated) || e.getMessage().contains("connect timed out");
    }
}
